package com.acip.uuid.segment;

import com.acip.uuid.manager.CosIdManager;
import com.acip.uuid.model.bo.SegmentIdDO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RList;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import static com.acip.uuid.constant.RedisKeysConstants.INIT_COS_IDS_CACHE_LOCK;

/**
 * @ClassName BusinessCosIdSegmentCache
 * @Description
 * @Author RayLiang
 * @Date 2022/11/28 15:43
 * @Version 1.0
 **/
@Slf4j
@Component
public class BusinessCosIdSegmentCache {
    /**
     * 业务号段map，号段采用链表形式往下面接
     */
    private final Map<String, BusinessCosIdSegmentChain> idGeneratorAutoIncrement = new ConcurrentHashMap<>();

    /**
     * 默认安全距离
     */
    private static long DEFAULT_SAFE_DISTANCE;

    /**
     * 默认预取号段数量
     */
    private static long DEFAULT_PREFETCHING_COUNT;

    /**
     * 饥饿阈值，单位秒
     */
    private static int HUNGER_THRESHOLD;


    private static String applicationName;

    private static final long MAX_PREFETCH_DISTANCE = Long.MAX_VALUE;

    @Resource
    private CosIdManager cosIdManager;

    @Resource
    private RedissonClient redissonClient;

    @Value("${segment.id.safe.distance:2}")
    public void setDefaultSafeDistance(long defaultSafeDistance) {
        this.DEFAULT_SAFE_DISTANCE = defaultSafeDistance;
    }

    @Value("${segment.id.prefetching.count:4}")
    public void setDefaultPrefetchingCount(long defaultPrefetchingCount) {
        this.DEFAULT_PREFETCHING_COUNT = defaultPrefetchingCount;
    }

    @Value("${segment.id.hunger.threshold:30}")
    public void setHungerThreshold(int hungerThreshold) {
        this.HUNGER_THRESHOLD = hungerThreshold;
    }

    @Value("${spring.application.name:}")
    public void setApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }
    /**
     * 初始化，并获取号段链
     *
     * @param businessId 业务标识
     * @return 号段链
     */
    public BusinessCosIdSegmentChain getBusinessCosIdGenerator(String businessId) {
        return idGeneratorAutoIncrement.computeIfAbsent(businessId, s ->
                new BusinessCosIdSegmentChain(businessId, DEFAULT_SAFE_DISTANCE, DEFAULT_PREFETCHING_COUNT, this::generatorSegmentChain));
    }

    /**
     * 生成号段链
     */
    public BusinessCosIdSegmentChain.BusinessCosIdGenerator generatorSegmentChain(BusinessCosIdSegmentChain businessCosIdSegmentChain) {

        String businessId = businessCosIdSegmentChain.getBusinessId();

        LocalDateTime putSegmentChainTime = businessCosIdSegmentChain.getPutSegmentChainTime();

        LocalDateTime hungerTime = businessCosIdSegmentChain.getHungerTime();

        long prefetchingCount;

        long wakeupTimeGap = Optional.ofNullable(putSegmentChainTime)
                .map(localDateTime -> Duration.between(putSegmentChainTime, hungerTime).toMillis() / 1000)
                .orElse((long) HUNGER_THRESHOLD);


        if (wakeupTimeGap < HUNGER_THRESHOLD) {
            log.debug("hungry.....");
            prefetchingCount = Math.min(Math.multiplyExact(businessCosIdSegmentChain.getLastPrefetchingCount(), Math.max(Math.floorDiv(HUNGER_THRESHOLD, Math.max(wakeupTimeGap, 1)), 2)), MAX_PREFETCH_DISTANCE);
        } else {
            log.debug("not hungry......");
            prefetchingCount = Math.max(Math.floorDiv(businessCosIdSegmentChain.getLastPrefetchingCount(), 2L), businessCosIdSegmentChain.getSafeDistance());
        }


        log.debug("本次预取号段 {}", prefetchingCount);
        businessCosIdSegmentChain.setLastPrefetchingCount(prefetchingCount);
        businessCosIdSegmentChain.setSafeDistance(Math.max(Math.floorDiv(prefetchingCount, 2L), DEFAULT_SAFE_DISTANCE));

        SegmentIdDO segmentIdDO = cosIdManager.getSegment(businessId, prefetchingCount);

        return new BusinessCosIdSegmentChain.BusinessCosIdGenerator(segmentIdDO.getBusinessId(), segmentIdDO.getStartId(), segmentIdDO.getEndId());
    }

    public  void saveCosidSegmentCache() {
        log.info("segment ids caching.....");
        try{
            RList<String> list = redissonClient.getList(applicationName.replaceAll("-", "_").concat("_cosid_segment_cache"));
            if(CollectionUtils.isEmpty(idGeneratorAutoIncrement)){
                return;
            }
            String[] watingJoinArray = new String[idGeneratorAutoIncrement.size()];
            int index = 0;
            for(Map.Entry<String, BusinessCosIdSegmentChain> chainEntry :idGeneratorAutoIncrement.entrySet()){
                BusinessCosIdSegmentChain.BusinessCosIdGenerator businessCosIdGeneratorFirst = chainEntry.getValue().peekFirst();
                if(businessCosIdGeneratorFirst.getEndId() == businessCosIdGeneratorFirst.getAtomicLong().get()){
                    chainEntry.getValue().poll();
                }
                businessCosIdGeneratorFirst = chainEntry.getValue().peekFirst();
                BusinessCosIdSegmentChain.BusinessCosIdGenerator businessCosIdGeneratorLast = chainEntry.getValue().peekLast();
                watingJoinArray[index] = chainEntry.getKey()+ ":" + businessCosIdGeneratorFirst.getAtomicLong() + ":" + businessCosIdGeneratorLast.getEndId();
                index++;
            }
            list.addLast(String.join("|",watingJoinArray));
        }catch(Exception e){
            log.error("cache segment error", e);
        }
        log.info("segment ids caching finish.....");

    }

    public void initCosidSegmentCache() {
        RLock lock = redissonClient.getLock(INIT_COS_IDS_CACHE_LOCK.concat(applicationName));
        try {
            lock.tryLock(20, 20, TimeUnit.MINUTES);
            RList<String> list = redissonClient.getList(applicationName.replaceAll("-", "_").concat("_cosid_segment_cache"));
            if(list.isEmpty()){
                return;
            }
            String chainListString = list.removeFirst();
            String[] segmentArrays = chainListString.split("[|]");
            for (int i = 0; i < segmentArrays.length; i++) {
                String[] split = segmentArrays[i].split(":");
                BusinessCosIdSegmentChain chain = new BusinessCosIdSegmentChain(split[0], DEFAULT_SAFE_DISTANCE, DEFAULT_PREFETCHING_COUNT, Long.valueOf(split[1]), Long.valueOf(split[2]), this::generatorSegmentChain);
                idGeneratorAutoIncrement.put(split[0],chain);
            }
        } catch (Exception e) {
            log.error("segment init error", e);
        } finally {
            if(Objects.nonNull(lock)){
                lock.unlock();
            }
        }
    }
}
